use crate::types::types::Func;
use once_cell::sync::Lazy;
use std::{collections::HashMap, f64::consts::PI};

pub const LINEAR: &str = "linear";
pub const EASE_IN_SINE: &str = "ease_in_sine";
pub const EASE_OUT_SINE: &str = "ease_out_sine";
pub const EASE_IN_OUT_SINE: &str = "ease_in_out_sine";

pub const EASE_IN_QUAD: &str = "ease_in_quad";
pub const EASE_OUT_QUAD: &str = "ease_out_quad";
pub const EASE_IN_OUT_QUAD: &str = "ease_in_out_quad";

pub const EASE_IN_CUBIC: &str = "ease_in_cubic";
pub const EASE_OUT_CUBIC: &str = "ease_out_cubic";
pub const EASE_IN_OUT_CUBIC: &str = "ease_in_out_cubic";

pub const EASE_IN_QUART: &str = "ease_in_quart";
pub const EASE_OUT_QUART: &str = "ease_out_quart";
pub const EASE_IN_OUT_QUART: &str = "ease_in_out_quart";

pub const EASE_IN_QUINT: &str = "ease_in_quint";
pub const EASE_OUT_QUINT: &str = "ease_out_quint";
pub const EASE_IN_OUT_QUINT: &str = "ease_in_out_quint";

pub const EASE_IN_BACK: &str = "ease_in_back";
pub const EASE_OUT_BACK: &str = "ease_out_back";
pub const EASE_IN_OUT_BACK: &str = "ease_in_out_back";

pub const EASE_IN_ELASTIC: &str = "ease_in_elastic";
pub const EASE_OUT_ELASTIC: &str = "ease_out_elastic";
pub const EASE_IN_OUT_ELASTIC: &str = "ease_in_out_elastic";

pub const EASE_IN_BOUNCE: &str = "ease_in_bounce";
pub const EASE_OUT_BOUNCE: &str = "ease_out_bounce";
pub const EASE_IN_OUT_BOUNCE: &str = "ease_in_out_bounce";

pub static CURVE_FUNC_MAP: Lazy<HashMap<&'static str, Func>> = Lazy::new(|| {
    let mut h = HashMap::new();
    h.insert(LINEAR, linear as _);
    h.insert(EASE_IN_SINE, ease_in_sine as _);
    h.insert(EASE_OUT_SINE, ease_out_sine as _);
    h.insert(EASE_IN_OUT_SINE, ease_in_out_sine as _);

    h.insert(EASE_IN_QUAD, ease_in_quad as _);
    h.insert(EASE_OUT_QUAD, ease_out_quad as _);
    h.insert(EASE_IN_OUT_QUAD, ease_in_out_quad as _);

    h.insert(EASE_IN_CUBIC, ease_in_cubic as _);
    h.insert(EASE_OUT_CUBIC, ease_out_cubic as _);
    h.insert(EASE_IN_OUT_CUBIC, ease_in_out_cubic as _);

    h.insert(EASE_IN_QUART, ease_in_quart as _);
    h.insert(EASE_OUT_QUART, ease_out_quart as _);
    h.insert(EASE_IN_OUT_QUART, ease_in_out_quart as _);

    h.insert(EASE_IN_QUINT, ease_in_quint as _);
    h.insert(EASE_OUT_QUINT, ease_out_quint as _);
    h.insert(EASE_IN_OUT_QUINT, ease_in_out_quint as _);

    h.insert(EASE_IN_BACK, ease_in_back as _);
    h.insert(EASE_OUT_BACK, ease_out_back as _);
    h.insert(EASE_IN_OUT_BACK, ease_in_out_back as _);

    h.insert(EASE_IN_ELASTIC, ease_in_elastic as _);
    h.insert(EASE_OUT_ELASTIC, ease_out_elastic as _);
    h.insert(EASE_IN_OUT_ELASTIC, ease_in_out_elastic as _);

    h.insert(EASE_IN_BOUNCE, ease_in_bounce as _);
    h.insert(EASE_OUT_BOUNCE, ease_out_bounce as _);
    h.insert(EASE_IN_OUT_BOUNCE, ease_in_out_bounce as _);
    h
});

pub fn get_frame_state_progress_by_funcs_map(name: &'static str, frame_num: f64) -> Vec<f64> {
    let (start_state, change_value) = (0., 1.);
    let t_gap = frame_num / (frame_num - 1.);
    let f = CURVE_FUNC_MAP.get(name);
    assert!(f.is_some());
    let f = f.unwrap();
    (0..frame_num as usize)
        .enumerate()
        .map(|(i, _)| f(i as f64 * t_gap, start_state, change_value, frame_num))
        .collect::<Vec<_>>()
}

fn linear(t: f64, b: f64, c: f64, d: f64) -> f64 {
    (c * t) / d + b
}

fn ease_in_sine(t: f64, b: f64, c: f64, d: f64) -> f64 {
    -c * ((t / d) * (PI / 2.)).cos() + c + b
}

fn ease_out_sine(t: f64, b: f64, c: f64, d: f64) -> f64 {
    -c * ((t / d) * (PI / 2.)).sin() + b
}

fn ease_in_out_sine(t: f64, b: f64, c: f64, d: f64) -> f64 {
    -c / 2. * ((PI * t / d).cos() - 1.0) + b
}

fn ease_in_quad(t: f64, b: f64, c: f64, d: f64) -> f64 {
    let t = t / d;
    c * t.powi(2) + b
}

fn ease_out_quad(t: f64, b: f64, c: f64, d: f64) -> f64 {
    let t = t / d;
    -c * t * (t - 2.) + b
}

fn ease_in_out_quad(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d / 2.;
    if t < 1. {
        return c / 2. * t.powi(2) + b;
    }
    t -= 1.;
    -c / 2. * (t * (t - 2.) - 1.) + b
}

fn ease_in_cubic(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d;
    c * t * t * t + b
}

fn ease_out_cubic(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t = t / d - 1.;
    c * (t * t * t + 1.) + b
}

fn ease_in_out_cubic(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d / 2.;
    if t < 1. {
        return c / 2. * t * t * t + b;
    }
    t -= 2.;
    c / 2. * (t * t * t + 2.) + b
}

fn ease_in_quart(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d;
    c * t * t * t * t + b
}

fn ease_out_quart(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t = t / d - 1.;
    -c * (t * t * t * t - 1.) + b
}

fn ease_in_out_quart(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d / 2.;
    if t < 1. {
        return c / 2. * t * t * t * t + b;
    }
    t -= 2.;
    -c / 2. * (t * t * t * t - 2.) + b
}

fn ease_in_quint(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d;
    c * t * t * t * t * t + b
}

fn ease_out_quint(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t = t / d - 1.;
    c * (t * t * t * t * t + 1.) + b
}

fn ease_in_out_quint(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d / 2.;
    if t < 1. {
        return c / 2. * t * t * t * t * t + b;
    }
    t -= 2.;
    c / 2. * (t * t * t * t * t + 2.) + b
}

fn ease_in_back(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    let s: f64 = 1.70158;
    t /= d;
    c * t * t * ((s + 1.) * t - s) + b
}

fn ease_out_back(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    let s: f64 = 1.70158;
    t = t / d - 1.;
    c * (t * t * ((s + 1.) * t + s) + 1.) + b
}

fn ease_in_out_back(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    let s: f64 = 1.70158 / 1.525;
    t /= d / 2.;
    if t < 1. {
        return c / 2. * t * t * ((s + 1.) * t - s) + b;
    }
    t -= 2.;
    c / 2. * (t * t * ((s + 1.) * t + s) + 2.) + b
}

fn ease_in_elastic(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    if t == 0. {
        return b;
    }
    t /= d;
    if t == 1. {
        return b + c;
    }

    t -= 1.;
    -(c * 2f64.powf(10. * t) * ((t * d - d * 0.3 / 4.) * (2. * PI) / (d * 0.3)).sin()) + b
}

fn ease_out_elastic(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    if t == 0. {
        return b;
    }

    t /= d;
    if t == 1. {
        return b + c;
    }
    c * 2f64.powf(-10. * t) * ((t * d - (d * 0.3) / 4.) * (2. * PI) / (d * 0.3)).sin() + c + b
}

fn ease_in_out_elastic(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    if t == 0. {
        return b;
    }

    t /= d / 2.;
    if t == 2. {
        return b + c;
    }
    let p = d * (0.3 * 1.5);
    let s = p / 4.;

    if t < 1. {
        t -= 1.;
        return -0.5 * (c * 2f64.powf(10. * t)) * ((t * d - s) * (2. * PI) / p).sin() + b;
    }
    t -= 1.;
    c * 2f64.powf(-10. * t) * ((t * d - s) * (2. * PI) / p).sin() * 0.5 + c + b
}

fn ease_out_bounce(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d;
    if t < 1. / 2.75 {
        return c * (7.5625 * t * t) + b;
    }
    if t < 2. / 2.75 {
        t -= 1.5 / 2.75;
        return c * (7.5625 * t * t + 0.75) + b;
    }

    if t < 2.5 / 2.75 {
        t -= 2.25 / 2.75;
        return c * (7.5625 * t * t + 0.9375) + b;
    }
    t -= 2.625 / 2.75;
    c * (7.5625 * t * t + 0.984375) + b
}

fn ease_in_bounce(t: f64, b: f64, c: f64, d: f64) -> f64 {
    c - ease_out_bounce(d - t, 0., c, d) + b
}

fn ease_in_out_bounce(t: f64, b: f64, c: f64, d: f64) -> f64 {
    if t < d / 2. {
        return ease_in_bounce(t * 2., 0., c, d) * 0.5 + b;
    }
    ease_out_bounce(t * 2. - d, 0., c, d) * 0.5 + c * 0.5 + b
}

#[allow(dead_code)]
fn ease_in_expo(t: f64, b: f64, c: f64, d: f64) -> f64 {
    if t == 0. {
        b
    } else {
        c * 2f64.powf(10. * (t / d - 1.)) + b
    }
}

#[allow(dead_code)]
fn ease_out_expo(t: f64, b: f64, c: f64, d: f64) -> f64 {
    if t == d {
        b + c
    } else {
        c * (-2f64.powf(-10. * t / d) + 1.) + b
    }
}

#[allow(dead_code)]
fn ease_in_out_expo(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    if t == 0. {
        return b;
    }

    if t == d {
        return b + c;
    }

    t /= d / 2.;
    if t < 1. {
        return (c / 2.) * 2f64.powf(10. * (t - 1.)) + b;
    }
    (c / 2.) * (-2f64.powf(-10. * (t - 1.)) + 2.) + b
}

#[allow(dead_code)]
fn ease_in_circ(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d;
    -c * ((1. - t * t).sqrt() - 1.) + b
}

#[allow(dead_code)]
fn ease_out_circ(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t = t / d - 1.;
    c * (1. - t * t).sqrt() + b
}

#[allow(dead_code)]
fn ease_in_out_circ(mut t: f64, b: f64, c: f64, d: f64) -> f64 {
    t /= d / 2.;
    if t < 1. {
        return (-c / 2.) * ((1. - t * t).sqrt() - 1.) + b;
    }
    t -= 2.;
    (c / 2.) * ((1. - t * t).sqrt() + 1.) + b
}
